package example.hellosecurity.config;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.sql.DataSource;

@Configuration
public class MySpringSecurityConfiguration extends WebSecurityConfigurerAdapter {


    @Autowired
    private  MyAccessDeniedHandler myAccessDeniedHandler;

    @Autowired
    private  PersistentTokenRepository persistentTokenRepository;

    @Autowired
    private UserSecurityService userSecurityService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        /*
        * 重写父类型的逻辑
        * 如果调用super.configure(http); 则使用父类型的默认流程配置
        * 需要自定义，则删除super.configure(http);方法的调用
        * http 基于http协议的security配置对象。包含所有的SpringSecurity相关的逻辑
        *
        * http.formLogin().loginProcessingUrl(Spring path)处理用户登录请求地址，处理用户登录逻辑地址
        * SpringSecurity提供的处理登录请求控制器，是path监听软编码的。可以通过该方法动态配置监听地址。
        * 只要配置地址和页面的请求地址一致即可完成登录逻辑。
        *成功登录返回成功页面是“/”
        * 登录失败默认返回页面是http.formLogin().loginProcessingUrl(Spring path)方法参数？error
        *
        * http.formLogin().defaultSuccessUrl(Spring path,boolean alwaysUse) 登录成功后响应重定向地址
        * Spring security要求传绝对路径则相当于于当前服务器的跟，开始寻址 http://localhost:port
        * */

        http.formLogin()
                .usernameParameter("name") //设置请求参数中 ，用户名参数名称。默认username
                .passwordParameter("pswd") // 设置请求参数中，密码擦数名称。默认password
                .loginPage("/toLogin") // 当用户未登录的时候跳转的页面是什么？默认login
                .loginProcessingUrl("/login")  //用户登录逻辑请求地址是什么。默认是/login
                .successHandler(new MyAuthenticationSuessHandler("/toMain",true))//使用自定义请求处理控制逻辑(Handler) 实现登录请求处理
                .failureHandler(new MyAuthenticationFailureHandler("/failure",true)); //使用自定义请求处理器控制逻辑实现登录失败
           // .failureUrl("/failure"); //登录失败后重定向的位置记得提供权限配置
 //              .failureForwardUrl("/failure");//登录失败后请求转发的位置，Securyity请求转发使用Post请求
 //              .successForwardUrl("/toMain");// 用户登录成功后请求转发道的位置。Security请求转发使用POST请求
//               .defaultSuccessUrl("/toMain" );// 用户登录成后响应重定向到的位置。get请求



        /*
        * 常用的匹配请求路径的方法执行顺序按照定义依次执行，任何方法匹配返回，后续逻辑不执行
        * 1.antMatchers - 程序员常用的是基于  * | /** 等匹配符号，定义表达式
         *antMatchers(String path);任意请求方式，都有效匹配，参数传入的规则任意一个符合匹配逻辑都生效，
         *如antMatchers("/a","/b","/c/*") 请求地址是  /a /b   /c/ 都符合
        *2.regexMatchers(String path)
         *regexMatchers (HttpMethod method,String... path);
        *3.anyRequest -相当于  /** 的antMatchers匹配，任意请求
         *

         */

/*
* 授予权限的方法逻辑
* 授予权限的方法，都是基于路径的匹配基础上调用的
* 常用的方法
*   permitAll() - 免登陆访问，任意用户任意状态都可以访问
*   anonymous() -匿名访问，登录不可访问，登录页面和登录请求
*   denyAll -任意用户任意状态都不可一访问
*   fullyAuthenticated() 完整登录才可有访问
*   rememberMe() 记住我，自动登录，n天免登陆
*
* 上述所有的方法，底层都是调用统一的方法，access()，
* access -可以实现任何权限授予逻辑，基于权限表达式的权限授予方法，
* 表达式规则由SpringSecrity定义
*
* */
       http.authorizeRequests()
               .antMatchers("/toLogin","favicon.ico").permitAll() //toLogin地址随便访问
               .antMatchers("/**/*.js").permitAll() //接收所有目录下的所有Js文件访问权限
               .regexMatchers(".*[.]css").permitAll() //接受所有目录下的所有.css文件可访问权限

        // 基于角色的权限管理
        //hasRole("xxx") 相当于调用access(hasRole('Role_xxx'))
               //hasAnyRole("x","y","z")相当于acc/guest/readess(hasArnyRole('ROLE_x','ROLE_y,'ROLE_z))
             //  .antMatchers("/laile").hasRole("管理员")//请求地址为/admin/read的请求，必须登录用户拥有‘管理员权限角色下课一访问’
            //  .antMatchers("/laile/nihao").hasAnyRole("管理员","访客")
             //  .antMatchers("/laile/write").hasRole("访客") //必须访客角色才可以访问*/
               .anyRequest().authenticated(); // 任意请求地址都必须认证才可以访问;

        /*
        * 配置403错误
        * 1.创建类实现AccessDeniedHandler 接口
        * 2.调用方法 httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
        * 3. httpServletResponse.setContentType("text/html;charset=UTF-8");
        * 4.httpServletResponse.getWriter().println("<html><body><h1>你没有权限</h1></body></html>");
        * 注入该对象myAccessDeniedHandler
        * */
        http.exceptionHandling()
                .accessDeniedHandler(myAccessDeniedHandler); //配置403错误访问



        /*
        * 配置rememberMe
        * */
        http.rememberMe()
                .rememberMeParameter("remember-me") //修改请求参数名，默认remember-me
                .tokenValiditySeconds(60)  //设置记住我有效时间。单位是秒 默认14天
                .tokenRepository(persistentTokenRepository) //配置用户登录标志的持久化工具对象
                .userDetailsService(userSecurityService); //配置自定义的UserDetailService接口实现类


        /*
        * 配置退出登录
        * */

        http.logout()
                .invalidateHttpSession(true) //回收httpSession对象。退出之前调用HttpSession.invalid
                .clearAuthentication(true) //退出之前，清空Security记录的用户登录标记 。默认true
                .logoutSuccessUrl("/toLogin") //配置退出后，进入的请求地址，默认是loginPage?Logout
                .logoutUrl("/logout");//配置退出登录的路径地址，和压面请求地址一致*/


        /*
        * 关闭csrf安全协议 关闭是为了保证完整流程可用
        * */

        http.csrf().disable();

    }







   /*
   * 创建一个持久化用户名登录标志Repository对象
   * JdbcTokenRepositoryImpl -对象，是访问数据库Repository,需要提供数据库连接
   * 所有被@Bean注解描述的方法，其参数，都自动从Spring容器获取对象
   * 如果没有对象，抛出异常，如果对象不唯一抛出异常
   *
   * security 持久化用户登录标记，使用表格由Security框架创建
   * 第一次使用的时候，没有表格，需要框架创建，后续使用的时候，表格存在，不能让框架创建表格否则抛出异常
   * */ 

@Bean
    public PersistentTokenRepository persistentTokenRepository(DataSource dataSource){

        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
//        设置启动的时候，创建表格 只有数据库不存在表格的时候可以使用
      //  jdbcTokenRepository.setCreateTableOnStartup(true);

        return jdbcTokenRepository;
    }
    
    

/*
* 把密码验证交给
* */
    @Bean
    public PasswordEncoder passwordEncoder(){
   return new BCryptPasswordEncoder();
    }


}
